{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Designing networks\n",
    "\n",
    "`Networks` are how Nengo encapsulates\n",
    "functionally complementary objects.\n",
    "Networks can contain\n",
    "ensembles, nodes, connection, even other networks.\n",
    "Models are often made much more readable\n",
    "my grouping objects into networks.\n",
    "Tools for visualizing networks\n",
    "will often use the network structure\n",
    "to produce cleaner images.\n",
    "\n",
    "Here, we will go over some general principles\n",
    "of network design. In the first section,\n",
    "we will go over these principles with a simple example.\n",
    "In the second section, we will apply\n",
    "these principles to a more complicated example.\n",
    "\n",
    "Briefly, the general principles we will\n",
    "go over in this tutorial are\n",
    "\n",
    "0. Group related objects in networks with `with`\n",
    "0. Reuse networks by defining functions\n",
    "0. Parameterize functions for more flexible reuse\n",
    "\n",
    "We will demonstrate these principles\n",
    "with a network consisting\n",
    "of two connected integrators\n",
    "as an example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import nengo\n",
    "from nengo.dists import Choice\n",
    "from nengo.processes import Piecewise\n",
    "from nengo.utils.ipython import hide_input\n",
    "\n",
    "\n",
    "def test_integrators(net):\n",
    "    with net:\n",
    "        piecewise = Piecewise({\n",
    "            0: 0,\n",
    "            0.2: 0.5,\n",
    "            1: 0,\n",
    "            2: -1,\n",
    "            3: 0,\n",
    "            4: 1,\n",
    "            5: 0\n",
    "        })\n",
    "        piecewise_inp = nengo.Node(piecewise)\n",
    "        nengo.Connection(piecewise_inp, net.pre_integrator.input)\n",
    "        input_probe = nengo.Probe(piecewise_inp)\n",
    "        pre_probe = nengo.Probe(net.pre_integrator.ensemble, synapse=0.01)\n",
    "        post_probe = nengo.Probe(net.post_integrator.ensemble, synapse=0.01)\n",
    "    with nengo.Simulator(net) as sim:\n",
    "        sim.run(6)\n",
    "    plt.figure()\n",
    "    plt.plot(sim.trange(), sim.data[input_probe], color='k')\n",
    "    plt.plot(sim.trange(), sim.data[pre_probe], color='b')\n",
    "    plt.plot(sim.trange(), sim.data[post_probe], color='g')\n",
    "\n",
    "\n",
    "hide_input()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Group related objects in networks with `with`\n",
    "\n",
    "All Nengo objects must be created\n",
    "in the context of some network.\n",
    "We use the `with` keyword to denote\n",
    "the network context in which\n",
    "a Nengo object is created.\n",
    "\n",
    "These `with` statements can be nested\n",
    "to group objects in appropriately named networks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Example: two connected integrators\n",
    "#  (See integrator example network for more details)\n",
    "net = nengo.Network(label=\"Two integrators\")\n",
    "with net:\n",
    "    with nengo.Network() as pre_integrator:\n",
    "        pre_input = nengo.Node(size_in=1)\n",
    "        pre_ensemble = nengo.Ensemble(100, dimensions=1)\n",
    "        nengo.Connection(pre_ensemble, pre_ensemble, synapse=0.1)\n",
    "        nengo.Connection(pre_input, pre_ensemble, synapse=None, transform=0.1)\n",
    "    with nengo.Network() as post_integrator:\n",
    "        post_input = nengo.Node(size_in=1)\n",
    "        post_ensemble = nengo.Ensemble(100, dimensions=1)\n",
    "        nengo.Connection(post_ensemble, post_ensemble, synapse=0.1)\n",
    "        nengo.Connection(\n",
    "            post_input, post_ensemble, synapse=None, transform=0.1)\n",
    "    nengo.Connection(pre_ensemble, post_input)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Test the integrators by giving piecewise input and plotting\n",
    "with net:\n",
    "    piecewise = Piecewise({0: 0, 0.2: 0.5, 1: 0, 2: -1, 3: 0, 4: 1, 5: 0})\n",
    "    piecewise_inp = nengo.Node(piecewise)\n",
    "    nengo.Connection(piecewise_inp, pre_input)\n",
    "    input_probe = nengo.Probe(piecewise_inp)\n",
    "    pre_probe = nengo.Probe(pre_ensemble, synapse=0.01)\n",
    "    post_probe = nengo.Probe(post_ensemble, synapse=0.01)\n",
    "with nengo.Simulator(net) as sim:\n",
    "    sim.run(6)\n",
    "plt.figure()\n",
    "plt.plot(sim.trange(), sim.data[input_probe], color='k', label=\"input\")\n",
    "plt.plot(sim.trange(), sim.data[pre_probe], color='b', label=\"pre integrator\")\n",
    "plt.plot(\n",
    "    sim.trange(), sim.data[post_probe], color='g', label=\"post integrator\")\n",
    "plt.legend(loc='best')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the `with` statements\n",
    "are not just cosmetic;\n",
    "Nengo objects are stored\n",
    "in the network that they're constructed in."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert pre_input in pre_integrator\n",
    "assert pre_input not in net\n",
    "assert pre_integrator in net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Reuse networks by defining functions\n",
    "\n",
    "Often, the same network is constructed\n",
    "more than once, resulting in duplicated code.\n",
    "We can reduce this code duplication\n",
    "by defining a function that\n",
    "creates the network and returns it.\n",
    "\n",
    "Even if you only use the network once,\n",
    "it can be helpful to define all of\n",
    "your subnetworks in functions\n",
    "to keep your code cleaner.\n",
    "Determining when a network\n",
    "should be defined in a function\n",
    "is informed by experience\n",
    "designing large networks\n",
    "and personal preference.\n",
    "\n",
    "### Store useful objects on the network\n",
    "\n",
    "A corollary to putting networks in functions\n",
    "is that any objects that you might want\n",
    "access to outside of that function\n",
    "should be stored on the network.\n",
    "Functions that create networks\n",
    "should only return that network.\n",
    "While all objects in that network\n",
    "are stored somewhere\n",
    "(e.g., ensembles are stored in a `net.ensembles` list),\n",
    "that list can be large and difficult to search.\n",
    "Therefore, we recommend storing objects\n",
    "on the network itself\n",
    "to make them accessible outside of the function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Example: two connected integrators. Now with functions!\n",
    "def Integrator1():\n",
    "    with nengo.Network() as integrator:\n",
    "        integrator.input = nengo.Node(size_in=1)\n",
    "        integrator.ensemble = nengo.Ensemble(100, dimensions=1)\n",
    "        nengo.Connection(integrator.ensemble, integrator.ensemble, synapse=0.1)\n",
    "        nengo.Connection(\n",
    "            integrator.input, integrator.ensemble, synapse=None, transform=0.1)\n",
    "    return integrator\n",
    "\n",
    "\n",
    "net = nengo.Network(label=\"Two integrators\")\n",
    "with net:\n",
    "    net.pre_integrator = Integrator1()\n",
    "    net.post_integrator = Integrator1()\n",
    "    nengo.Connection(net.pre_integrator.ensemble, net.post_integrator.input)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This function does the same as the previous example\n",
    "test_integrators(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Parameterize functions for more flexible reuse\n",
    "\n",
    "Now that your network is created in a function,\n",
    "you can add parameters to your function\n",
    "to change how your network is constructed.\n",
    "This could be a huge change,\n",
    "allowing you to compare two different approaches\n",
    "to some component of your model,\n",
    "or it could something straightforward\n",
    "like changing the number of neurons\n",
    "in the ensembles in the network.\n",
    "\n",
    "Unlike other languages, Python allows you\n",
    "to add two different types of parameters:\n",
    "**positional** and **keyword** arguments.\n",
    "Positional arguments must be passed\n",
    "to your function.\n",
    "\n",
    "```python\n",
    ">>> def my_network(arg1, arg2):\n",
    "...     return \"%s %s\" % (arg1, arg2)\n",
    ">>> my_network()\n",
    "TypeError: my_network() takes exactly 2 arguments (0 given)\n",
    ">>> my_network(0, 0)\n",
    "'0 0'\n",
    "```\n",
    "\n",
    "Keyword arguments are assigned a default value,\n",
    "and so do not have to be passed to your function.\n",
    "\n",
    "```python\n",
    ">>> def my_network(arg1=0, arg2=0):\n",
    "...     return \"%s %s\" % (arg1, arg2)\n",
    ">>> my_network()\n",
    "'0 0'\n",
    ">>> my_network(1)\n",
    "'1 0'\n",
    "```\n",
    "\n",
    "Order does not matter if you explicitly\n",
    "label the argument in your function call.\n",
    "\n",
    "```python\n",
    ">>> my_network(arg2=2, arg1=1)\n",
    "'1 2'\n",
    ">>> my_network(arg2=2)\n",
    "'0 2'\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Example: two connected integrators. Functions have parameters!\n",
    "def Integrator2(n_neurons, dimensions, tau=0.1):\n",
    "    with nengo.Network() as integrator:\n",
    "        integrator.input = nengo.Node(size_in=dimensions)\n",
    "        integrator.ensemble = nengo.Ensemble(n_neurons, dimensions=dimensions)\n",
    "        nengo.Connection(integrator.ensemble, integrator.ensemble, synapse=tau)\n",
    "        nengo.Connection(\n",
    "            integrator.input, integrator.ensemble, synapse=None, transform=tau)\n",
    "    return integrator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Try 50 neuron ensembles\n",
    "net = nengo.Network(label=\"Two integrators\")\n",
    "with net:\n",
    "    net.pre_integrator = Integrator2(50, 1)\n",
    "    net.post_integrator = Integrator2(50, 1)\n",
    "    nengo.Connection(net.pre_integrator.ensemble, net.post_integrator.input)\n",
    "test_integrators(net)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Try shorter tau (spoiler: it fails to integrate well)\n",
    "net = nengo.Network(label=\"Two integrators\")\n",
    "with net:\n",
    "    net.pre_integrator = Integrator2(50, 1, tau=0.01)\n",
    "    net.post_integrator = Integrator2(50, 1, tau=0.01)\n",
    "    nengo.Connection(net.pre_integrator.ensemble, net.post_integrator.input)\n",
    "test_integrators(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Longer example: double integrator network\n",
    "\n",
    "While the coupled integrator model above\n",
    "seems like a toy example,\n",
    "it is in fact a critical component\n",
    "of many models that capture\n",
    "neurophysiological and behavioral data.\n",
    "Let's looks at a more complicated network\n",
    "that uses coupled integrators,\n",
    "and show how the network design principles\n",
    "discussed above improve model organization.\n",
    "We will look at a simplified version\n",
    "of the network described in\n",
    "[Bekolay et al., 2014](\n",
    "http://compneuro.uwaterloo.ca/files/publications/bekolay.2014a.pdf).\n",
    "\n",
    "This network uses two coupled integrators\n",
    "in the context of a simple reaction time task.\n",
    "The subject presses down a lever,\n",
    "and should release that lever\n",
    "between 0.6 and 1.0 seconds later.\n",
    "In order to time the interval,\n",
    "the lever press starts the coupled integrators,\n",
    "which are tuned such that\n",
    "the second integrator should reach\n",
    "a high point by around 0.6 seconds.\n",
    "The second integrator\n",
    "effects the lever release.\n",
    "\n",
    "We'll start with a completely disorganized network.\n",
    "Even if you've read the paper,\n",
    "the code is quite hard to follow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with nengo.Network() as net:\n",
    "    tau = 0.1\n",
    "\n",
    "    pre_integrator = nengo.Ensemble(200, dimensions=2)\n",
    "    nengo.Connection(\n",
    "        pre_integrator,\n",
    "        pre_integrator[0],\n",
    "        function=lambda x: x[0] * (1.0 - x[1]),\n",
    "        synapse=tau)\n",
    "    post_integrator = nengo.Ensemble(200, dimensions=2)\n",
    "    nengo.Connection(\n",
    "        post_integrator,\n",
    "        post_integrator[0],\n",
    "        function=lambda x: x[0] * (1.0 - x[1]),\n",
    "        synapse=tau)\n",
    "    nengo.Connection(pre_integrator[0], post_integrator[0], transform=0.16)\n",
    "    press = nengo.Ensemble(\n",
    "        30, dimensions=1, encoders=Choice([[1]]), intercepts=Choice([0.85]))\n",
    "    release = nengo.Ensemble(\n",
    "        30, dimensions=1, encoders=Choice([[1]]), intercepts=Choice([0.85]))\n",
    "\n",
    "    nengo.Connection(press, pre_integrator[0], transform=tau * 3)\n",
    "    nengo.Connection(press, post_integrator[1], transform=-1 * 6)\n",
    "\n",
    "    nengo.Connection(post_integrator[0], release)\n",
    "    nengo.Connection(nengo.Node(lambda t: 1 if t < 0.2 else 0), press)\n",
    "\n",
    "    pr_press = nengo.Probe(press, synapse=0.01)\n",
    "    pr_release = nengo.Probe(release, synapse=0.01)\n",
    "    pr_pre_int = nengo.Probe(pre_integrator[0], synapse=0.01)\n",
    "    pr_post_int = nengo.Probe(post_integrator[0], synapse=0.01)\n",
    "\n",
    "\n",
    "def test_doubleintegrator(net):\n",
    "    with nengo.Simulator(net) as sim:\n",
    "        sim.run(1.4)\n",
    "    t = sim.trange()\n",
    "    plt.figure()\n",
    "    plt.subplot(2, 1, 1)\n",
    "    plt.plot(t, sim.data[pr_press], c='b', label=\"Press\")\n",
    "    plt.plot(t, sim.data[pr_release], c='g', label=\"Release\")\n",
    "    plt.axvspan(0, 0.2, color='b', alpha=0.3)\n",
    "    plt.axvspan(0.8, 1.2, color='g', alpha=0.3)\n",
    "    plt.xlim(right=1.4)\n",
    "    plt.legend(loc=\"best\")\n",
    "    plt.subplot(2, 1, 2)\n",
    "    plt.plot(t, sim.data[pr_pre_int], label=\"Pre Integrator\")\n",
    "    plt.plot(t, sim.data[pr_post_int], label=\"Post Integrator\")\n",
    "    plt.xlim(right=1.4)\n",
    "    plt.legend(loc=\"best\")\n",
    "\n",
    "\n",
    "test_doubleintegrator(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Recall that the goal is to release the lever\n",
    "0.6-1.0 seconds after the press.\n",
    "The blue \"Press\" line in the upper plot\n",
    "represents the activity of neurons\n",
    "in motor cortex which effect the lever press.\n",
    "If we assume that the press occurs at 0.2 seconds,\n",
    "then the release window is between 0.8 and 1.2 seconds\n",
    "(shown in green).\n",
    "This will occur if the \"Post Integrator\" (see lower plot)\n",
    "is sufficiently high during this window.\n",
    "If you rerun the cell above several times,\n",
    "you can see that it usually achieves this,\n",
    "but not all of the time.\n",
    "\n",
    "Looking at the code, it's difficult to tell\n",
    "how we achieve this result.\n",
    "It's also difficult to see what parameters\n",
    "needed tweaking.\n",
    "Rather than just showing one parameter set that works,\n",
    "we should investigate reasonable ranges for\n",
    "each parameter.\n",
    "It would be easy to think that there is only\n",
    "one parameter, `tau`, in the model as presented,\n",
    "but this is not the case.\n",
    "\n",
    "Let's start with the first network design principle:\n",
    "group objects into networks with `with`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with nengo.Network() as net:\n",
    "    tau = 0.1\n",
    "\n",
    "    with nengo.Network() as mpfc:\n",
    "        with nengo.Network() as pre_integrator:\n",
    "            pre_ensemble = nengo.Ensemble(200, dimensions=2)\n",
    "            nengo.Connection(\n",
    "                pre_ensemble,\n",
    "                pre_ensemble[0],\n",
    "                function=lambda x: x[0] * (1.0 - x[1]),\n",
    "                synapse=tau)\n",
    "        with nengo.Network() as post_integrator:\n",
    "            post_ensemble = nengo.Ensemble(200, dimensions=2)\n",
    "            nengo.Connection(\n",
    "                post_ensemble,\n",
    "                post_ensemble[0],\n",
    "                function=lambda x: x[0] * (1.0 - x[1]),\n",
    "                synapse=tau)\n",
    "        nengo.Connection(pre_ensemble[0], post_ensemble[0], transform=0.16)\n",
    "\n",
    "    with nengo.Network() as motor:\n",
    "        press = nengo.Ensemble(\n",
    "            30,\n",
    "            dimensions=1,\n",
    "            encoders=Choice([[1]]),\n",
    "            intercepts=Choice([0.85]))\n",
    "        release = nengo.Ensemble(\n",
    "            30,\n",
    "            dimensions=1,\n",
    "            encoders=Choice([[1]]),\n",
    "            intercepts=Choice([0.85]))\n",
    "\n",
    "    nengo.Connection(press, pre_ensemble[0], transform=tau * 3)\n",
    "    nengo.Connection(press, post_ensemble[1], transform=-1 * 6)\n",
    "\n",
    "    nengo.Connection(post_ensemble[0], release)\n",
    "    nengo.Connection(nengo.Node(lambda t: 1 if t < 0.2 else 0), press)\n",
    "\n",
    "    pr_press = nengo.Probe(press, synapse=0.01)\n",
    "    pr_release = nengo.Probe(release, synapse=0.01)\n",
    "    pr_pre_int = nengo.Probe(pre_ensemble[0], synapse=0.01)\n",
    "    pr_post_int = nengo.Probe(post_ensemble[0], synapse=0.01)\n",
    "\n",
    "test_doubleintegrator(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This exhibits some of the hypotheses of this model;\n",
    "specifically, that the medial prefrontal cortex\n",
    "contains these integrators,\n",
    "and that they can be involved\n",
    "in control of motor actions.\n",
    "\n",
    "Let's do both of the remaining principles\n",
    "by making some parameterized functions\n",
    "to get a sense of all of the knobs\n",
    "that be be tweaked in this model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def controlled_integrator(n_neurons, dimensions, tau=0.1):\n",
    "    with nengo.Network() as integrator:\n",
    "        integrator.ensemble = nengo.Ensemble(\n",
    "            n_neurons, dimensions=dimensions + 1)\n",
    "        nengo.Connection(\n",
    "            integrator.ensemble,\n",
    "            integrator.ensemble[:dimensions],\n",
    "            function=lambda x: x[:-1] * (1.0 - x[-1]),\n",
    "            synapse=tau)\n",
    "    return integrator\n",
    "\n",
    "\n",
    "def medial_pfc(coupling_strength, n_neurons_per_integrator=200, tau=0.1):\n",
    "    with nengo.Network() as medial_pfc:\n",
    "        medial_pfc.pre = controlled_integrator(n_neurons_per_integrator, 1,\n",
    "                                               tau)\n",
    "        medial_pfc.post = controlled_integrator(n_neurons_per_integrator, 1,\n",
    "                                                tau)\n",
    "        nengo.Connection(\n",
    "            medial_pfc.pre.ensemble[0],\n",
    "            medial_pfc.post.ensemble[0],\n",
    "            transform=coupling_strength)\n",
    "    return medial_pfc\n",
    "\n",
    "\n",
    "def motor_cortex(command_threshold, n_neurons_per_command=30):\n",
    "    with nengo.Network() as motor_cortex:\n",
    "        ens_args = {\n",
    "            'dimensions': 1,\n",
    "            'encoders': Choice([[1]]),\n",
    "            'intercepts': Choice([command_threshold])\n",
    "        }\n",
    "        motor_cortex.press = nengo.Ensemble(n_neurons_per_command, **ens_args)\n",
    "        motor_cortex.release = nengo.Ensemble(n_neurons_per_command,\n",
    "                                              **ens_args)\n",
    "    return motor_cortex\n",
    "\n",
    "\n",
    "def double_integrator(mpfc_coupling_strength,\n",
    "                      command_threshold,\n",
    "                      press_to_pre_gain=3,\n",
    "                      press_to_post_control=-6,\n",
    "                      recurrent_tau=0.1,\n",
    "                      seed=None):\n",
    "    with nengo.Network(seed=seed) as net:\n",
    "        net.mpfc = medial_pfc(mpfc_coupling_strength)\n",
    "        net.motor = motor_cortex(command_threshold)\n",
    "        nengo.Connection(\n",
    "            net.motor.press,\n",
    "            net.mpfc.pre.ensemble[0],\n",
    "            transform=recurrent_tau * press_to_pre_gain)\n",
    "        nengo.Connection(\n",
    "            net.motor.press,\n",
    "            net.mpfc.post.ensemble[1],\n",
    "            transform=press_to_post_control)\n",
    "        nengo.Connection(net.mpfc.post.ensemble[0], net.motor.release)\n",
    "    return net\n",
    "\n",
    "\n",
    "def test_doubleintegrator_wprobes(net):\n",
    "    # Provide input and probe outside of network construction,\n",
    "    # for more flexibility\n",
    "    with net:\n",
    "        nengo.Connection(\n",
    "            nengo.Node(lambda t: 1 if t < 0.2 else 0), net.motor.press)\n",
    "        pr_press = nengo.Probe(net.motor.press, synapse=0.01)\n",
    "        pr_release = nengo.Probe(net.motor.release, synapse=0.01)\n",
    "        pr_pre_int = nengo.Probe(net.mpfc.pre.ensemble[0], synapse=0.01)\n",
    "        pr_post_int = nengo.Probe(net.mpfc.post.ensemble[0], synapse=0.01)\n",
    "    with nengo.Simulator(net) as sim:\n",
    "        sim.run(1.4)\n",
    "    t = sim.trange()\n",
    "    plt.figure()\n",
    "    plt.subplot(2, 1, 1)\n",
    "    plt.plot(t, sim.data[pr_press], c='b', label=\"Press\")\n",
    "    plt.plot(t, sim.data[pr_release], c='g', label=\"Release\")\n",
    "    plt.axvspan(0, 0.2, color='b', alpha=0.3)\n",
    "    plt.axvspan(0.8, 1.2, color='g', alpha=0.3)\n",
    "    plt.xlim(right=1.4)\n",
    "    plt.legend(loc=\"best\")\n",
    "    plt.subplot(2, 1, 2)\n",
    "    plt.plot(t, sim.data[pr_pre_int], label=\"Pre Integrator\")\n",
    "    plt.plot(t, sim.data[pr_post_int], label=\"Post Integrator\")\n",
    "    plt.xlim(right=1.4)\n",
    "    plt.legend(loc=\"best\")\n",
    "\n",
    "\n",
    "net = double_integrator(mpfc_coupling_strength=0.16, command_threshold=0.85)\n",
    "test_doubleintegrator_wprobes(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While this does add some complexity and length,\n",
    "the effort is worth it,\n",
    "as we can now do simple parameters sweeps\n",
    "to investigate the effects\n",
    "of changing various parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for coupling_strength in (0.11, 0.16, 0.21):\n",
    "    net = double_integrator(\n",
    "        mpfc_coupling_strength=coupling_strength,\n",
    "        command_threshold=0.85,\n",
    "        seed=0)\n",
    "    test_doubleintegrator_wprobes(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `coupling_strength` changes how quickly\n",
    "the \"Post Integrator\" rises,\n",
    "which causes the release to occur\n",
    "at different times.\n",
    "Of the values tested,\n",
    "only a coupling strength of 0.16\n",
    "effects the release during the release window\n",
    "(for this particular network seed).\n",
    "\n",
    "We will looks at some more techniques\n",
    "for cleaning up this network code\n",
    "and making it more flexible\n",
    "in \"Additional tips and tricks for designing networks\"."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
